package com.xiaoshuidi.cloud.module.finance.service.finance.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.PhoneUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.google.common.collect.Sets;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Element;
import com.itextpdf.text.Font;
import com.itextpdf.text.Paragraph;
import com.xiaoshuidi.cloud.framework.common.enums.CommonStatusEnum;
import com.xiaoshuidi.cloud.framework.common.exception.ServiceException;
import com.xiaoshuidi.cloud.framework.common.pojo.CommonResult;
import com.xiaoshuidi.cloud.framework.common.pojo.PageResult;
import com.xiaoshuidi.cloud.framework.common.util.json.JsonUtils;
import com.xiaoshuidi.cloud.framework.redis.lock.DistributeLock;
import com.xiaoshuidi.cloud.framework.redis.sequence.TimebaseSequenceGenerator;
import com.xiaoshuidi.cloud.framework.security.core.LoginUser;
import com.xiaoshuidi.cloud.framework.security.core.util.SecurityFrameworkUtils;
import com.xiaoshuidi.cloud.framework.tenant.core.util.TenantUtils;
import com.xiaoshuidi.cloud.module.bpm.api.task.BpmProcessInstanceApi;
import com.xiaoshuidi.cloud.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import com.xiaoshuidi.cloud.module.bpm.api.task.dto.BpmTaskRespDTO;
import com.xiaoshuidi.cloud.module.bpm.enums.BpmBusinessTypeEnum;
import com.xiaoshuidi.cloud.module.bpm.enums.task.BpmProcessInstanceResultEnum;
import com.xiaoshuidi.cloud.module.bpm.mq.message.BpmProcessInstanceResultEvent;
import com.xiaoshuidi.cloud.module.contract.api.ApprovalApi;
import com.xiaoshuidi.cloud.module.contract.api.ContractApi;
import com.xiaoshuidi.cloud.module.contract.api.ContractRoomAccountApi;
import com.xiaoshuidi.cloud.module.contract.api.CustomerCompanyApi;
import com.xiaoshuidi.cloud.module.contract.enums.ApproveTypeEnum;
import com.xiaoshuidi.cloud.module.contract.enums.contract.CustomerTypeEnum;
import com.xiaoshuidi.cloud.module.contract.vo.*;
import com.xiaoshuidi.cloud.module.finance.api.finance.dto.BankTransferReqDTO;
import com.xiaoshuidi.cloud.module.finance.controller.admin.finance.vo.account.*;
import com.xiaoshuidi.cloud.module.finance.controller.app.finance.vo.AppFinanceAccountRechargeReqVO;
import com.xiaoshuidi.cloud.module.finance.controller.app.finance.vo.AppFinanceAccountRefundReqVO;
import com.xiaoshuidi.cloud.module.finance.convert.finance.FinanceConvert;
import com.xiaoshuidi.cloud.module.finance.dal.dataobject.finance.*;
import com.xiaoshuidi.cloud.module.finance.dal.mysql.finance.FinanceAccountJournalMapper;
import com.xiaoshuidi.cloud.module.finance.dal.mysql.finance.FinanceAccountMapper;
import com.xiaoshuidi.cloud.module.finance.dal.mysql.finance.FinanceAccountWithdrawMapper;
import com.xiaoshuidi.cloud.module.finance.dal.mysql.finance.FinanceMoneyFlowMapper;
import com.xiaoshuidi.cloud.module.finance.dal.redis.RedisKeyConstants;
import com.xiaoshuidi.cloud.module.finance.enums.ErrorCodeConstants;
import com.xiaoshuidi.cloud.module.finance.enums.finance.*;
import com.xiaoshuidi.cloud.module.finance.enums.order.FinanceCommonStatusEnum;
import com.xiaoshuidi.cloud.module.finance.mq.message.FinanceAccountDeductMessage;
import com.xiaoshuidi.cloud.module.finance.mq.message.PayResultMessage;
import com.xiaoshuidi.cloud.module.finance.mq.producer.FinanceAccountBalanceProducer;
import com.xiaoshuidi.cloud.module.finance.service.finance.FinanceAccountBalanceWarningService;
import com.xiaoshuidi.cloud.module.finance.service.finance.FinanceAccountService;
import com.xiaoshuidi.cloud.module.finance.service.finance.FinanceBankTransferService;
import com.xiaoshuidi.cloud.module.finance.service.finance.FinanceMoneyFlowService;
import com.xiaoshuidi.cloud.module.finance.util.pdf.PdfUtils;
import com.xiaoshuidi.cloud.module.member.api.user.MemberUserApi;
import com.xiaoshuidi.cloud.module.member.api.user.dto.MemberUserPageDTO;
import com.xiaoshuidi.cloud.module.member.api.user.dto.MemberUserRespDTO;
import com.xiaoshuidi.cloud.module.system.api.sms.SmsSendApi;
import com.xiaoshuidi.cloud.module.system.api.sms.dto.send.SmsSendSingleToUserReqDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import static com.xiaoshuidi.cloud.framework.security.core.util.SecurityFrameworkUtils.getLoginUser;
import static com.xiaoshuidi.cloud.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;

@Slf4j
@Service
public class FinanceAccountServiceImpl implements FinanceAccountService {
    @Resource
    private FinanceAccountMapper financeAccountMapper;
    @Resource
    private FinanceAccountJournalMapper financeAccountJournalMapper;
    @Resource
    private FinanceBankTransferService financeBankTransferService;
    @Resource
    private TransactionTemplate transactionTemplate;
    @Resource
    private FinanceAccountBalanceProducer financeAccountBalanceProducer;
    @Resource
    private ContractApi contractApi;
    @Resource
    private MemberUserApi memberUserApi;
    @Resource
    private CustomerCompanyApi customerCompanyApi;
    @Resource
    private ContractRoomAccountApi contractRoomAccountApi;
    @Resource
    private FinanceMoneyFlowMapper financeMoneyFlowMapper;
    @Resource
    private FinanceMoneyFlowService financeMoneyFlowService;
    @Resource
    private FinanceAccountBalanceWarningService financeAccountBalanceWarningService;
    @Resource
    private SmsSendApi smsSendApi;

    @Resource
    private ApprovalApi approvalApi;

    @Value("${spring.application.name}")
    private String applicationName;
    @Resource
    private BpmProcessInstanceApi bpmProcessInstanceApi;

    @Resource
    private TimebaseSequenceGenerator timebaseSequenceGenerator;

    @Resource
    private FinanceAccountWithdrawMapper financeAccountWithdrawMapper;

    @Override
    public void recharge(FinanceAccountCreateReqVO reqVO) {
        try (DistributeLock lock = getLock(reqVO.getCustId(), reqVO.getCustType())) {
            if (!lock.tryLock()) {
                throw new ServiceException(ErrorCodeConstants.FINANCE_ACCOUNT_CONCURRENT_MODIFY);
            }
            log.info("[recharge] 充值 {}", JsonUtils.toJsonString(reqVO));
            transactionTemplate.executeWithoutResult(status -> {
                Long apartmentId = null;
                String apartmentName = null;
                Long roomId = null;
                String roomName = null;
                CommonResult<List<ContractRoomReqVO>> result = null;
                if (FinanceCustomerTypeEnum.INDIVIDUAL.name().equals(reqVO.getCustType())) {
                    result = contractApi.getAllContractByUserId(Long.parseLong(reqVO.getCustId()));
                } else if (FinanceCustomerTypeEnum.COMPANY.name().equals(reqVO.getCustType())) {
                    result = contractApi.getAllContractByCompanyId(Long.parseLong(reqVO.getCustId()));
                } else if (FinanceCustomerTypeEnum.ROOM.name().equals(reqVO.getCustType())) {
                    String[] s = reqVO.getCustId().split("_");
                    result = contractApi.getAllContractByCompanyIdAndRoomId(Long.parseLong(s[0]), Long.parseLong(s[1]));
                }
                if (result != null && result.isSuccess() && !CollectionUtils.isEmpty(result.getData())) {
                    apartmentId = result.getData().get(0).getApartmentId();
                    apartmentName = result.getData().get(0).getApartmentName();
                    roomId = result.getData().get(0).getRoomId();
                    roomName = result.getData().get(0).getRoomName();
                }

                FinanceAccountDO account = financeAccountMapper.selectByCustIdAndCustType(reqVO.getCustId(),
                        reqVO.getCustType());
                if (account == null) {
                    account = create(reqVO.getCustId(), reqVO.getCustType());
                }
                BigDecimal balance = account.getBalance();
                BigDecimal endingBalance = balance.add(reqVO.getRechargeAmount());

                FinanceAccountJournalDO accountJournalDO = FinanceAccountJournalDO.builder()
                        .accountId(account.getId())
                        .subject(AccountTradeTypeEnum.getDesc(reqVO.getTradeType()))
                        .tradeTime(reqVO.getTradeDate() == null ? LocalDateTime.now() : reqVO.getTradeDate())
                        .tradeType(reqVO.getTradeType())
                        .tradeAccountNo(reqVO.getAccountNo())
                        .tradeBank(reqVO.getAccountBank())
                        .operator(reqVO.getOperator())
                        .beginningBalance(balance)
                        .endingBalance(endingBalance)
                        .amount(reqVO.getRechargeAmount())
                        .remark(reqVO.getRemark())
                        .attachment(reqVO.getAttachment())
                        .direction(DirectionEnum.IN.getValue())
                        .status(FinanceCommonStatusEnum.WAITING.name())
                        .build();

                financeAccountJournalMapper.insert(accountJournalDO);
                // 充值交易流水
                FinanceMoneyFlowDO financeMoneyFlowDO = new FinanceMoneyFlowDO();
                financeMoneyFlowDO.setFlowNo(financeMoneyFlowService.nextFlowNo());
                financeMoneyFlowDO.setTradeAccount(reqVO.getAccountNo());
                financeMoneyFlowDO.setSourceFrom(PaymentSourceEnum.OFFLINE.name());
                financeMoneyFlowDO.setTradeAmount(reqVO.getRechargeAmount());
                financeMoneyFlowDO.setPayMethod("银行转账");
                financeMoneyFlowDO.setTrader(reqVO.getAccountName());
                financeMoneyFlowDO.setOperator(reqVO.getOperator());
                financeMoneyFlowDO.setTradeTime(LocalDateTime.now());
                financeMoneyFlowDO.setStatus(FinanceCommonStatusEnum.WAITING.name());
                financeMoneyFlowDO.setSourceFrom(PaymentSourceEnum.OFFLINE.name());
                financeMoneyFlowDO.setSourceBizId(accountJournalDO.getId().toString());
                financeMoneyFlowDO.setSourceBizType(SourceBizTypeEnum.ACCOUNT_RECHARGE.name());
                financeMoneyFlowDO.setRemark(reqVO.getRemark());
                financeMoneyFlowDO.setAttachment(reqVO.getAttachment());
                financeMoneyFlowDO.setOperator(reqVO.getOperator());
                financeMoneyFlowDO.setRentCustomer(account.getCustName());
                financeMoneyFlowDO.setRenter(account.getCustName());
                financeMoneyFlowDO.setApartmentId(apartmentId);
                financeMoneyFlowDO.setApartmentName(apartmentName);
                financeMoneyFlowDO.setRoomId(roomId);
                financeMoneyFlowDO.setRoomName(roomName);
                financeMoneyFlowMapper.insert(financeMoneyFlowDO);
            });

        }
    }

    @Override
    public void deduction(FinanceAccountCreateReqVO reqVO) {
        try (DistributeLock lock = getLock(reqVO.getCustId(), reqVO.getCustType())) {
            if (!lock.tryLock()) {
                throw new ServiceException(ErrorCodeConstants.FINANCE_ACCOUNT_CONCURRENT_MODIFY);
            }
            log.info("[deduction] 充值 {}", JsonUtils.toJsonString(reqVO));
            transactionTemplate.executeWithoutResult(status -> {
                Long apartmentId = null;
                String apartmentName = null;
                CommonResult<List<ContractRoomReqVO>> result = null;
                if (FinanceCustomerTypeEnum.INDIVIDUAL.name().equals(reqVO.getCustType())) {
                    result = contractApi.getAllContractByUserId(Long.parseLong(reqVO.getCustId()));
                } else if (FinanceCustomerTypeEnum.COMPANY.name().equals(reqVO.getCustType())) {
                    result = contractApi.getAllContractByCompanyId(Long.parseLong(reqVO.getCustId()));
                }
                if (result != null && result.isSuccess() && !CollectionUtils.isEmpty(result.getData())) {
                    apartmentId = result.getData().get(0).getApartmentId();
                    apartmentName = result.getData().get(0).getApartmentName();
                }

                FinanceAccountDO account = financeAccountMapper.selectByCustIdAndCustType(reqVO.getCustId(),
                        reqVO.getCustType());
                if (account == null) {
                    throw new ServiceException(ErrorCodeConstants.FINANCE_ACCOUNT_NOT_EXISTS);
                }


                BigDecimal balance = account.getBalance();
                //扣费
                BigDecimal endingBalance = balance.subtract(reqVO.getRechargeAmount());

                //更新钱包
                financeAccountMapper.updateById(FinanceAccountDO.builder().id(account.getId()).balance(endingBalance).build());

                FinanceAccountJournalDO accountJournalDO = FinanceAccountJournalDO.builder()
                        .accountId(account.getId())
                        .subject("线下扣费")
                        .tradeTime(LocalDateTime.now())
                        .tradeType(AccountTradeTypeEnum.DEDUCT.name())
                        .tradeAccountNo(reqVO.getAccountNo())
                        .tradeBank(reqVO.getAccountBank())
                        .operator(reqVO.getOperator())
                        .beginningBalance(balance)
                        .endingBalance(endingBalance)
                        .amount(reqVO.getRechargeAmount())
                        .remark(reqVO.getRemark())
                        .attachment(reqVO.getAttachment())
                        .direction(DirectionEnum.OUT.getValue())
                        .status(FinanceCommonStatusEnum.SUCCESS.name())
                        .build();

                financeAccountJournalMapper.insert(accountJournalDO);
                // 充值交易流水
                FinanceMoneyFlowDO financeMoneyFlowDO = new FinanceMoneyFlowDO();
                financeMoneyFlowDO.setFlowNo(financeMoneyFlowService.nextFlowNo());
                financeMoneyFlowDO.setTradeAccount(reqVO.getAccountNo());
                financeMoneyFlowDO.setSourceFrom(PaymentSourceEnum.OFFLINE.name());
                financeMoneyFlowDO.setTradeAmount(reqVO.getRechargeAmount());
                financeMoneyFlowDO.setPayMethod("银行转账");
                financeMoneyFlowDO.setTrader(reqVO.getAccountName());
                financeMoneyFlowDO.setOperator(reqVO.getOperator());
                financeMoneyFlowDO.setTradeTime(LocalDateTime.now());
                financeMoneyFlowDO.setStatus(FinanceCommonStatusEnum.SUCCESS.name());
                financeMoneyFlowDO.setSourceFrom(PaymentSourceEnum.OFFLINE.name());
                financeMoneyFlowDO.setSourceBizId(accountJournalDO.getId().toString());
                financeMoneyFlowDO.setSourceBizType(SourceBizTypeEnum.ACCOUNT_DEDUCTION.name());
                financeMoneyFlowDO.setRemark(reqVO.getRemark());
                financeMoneyFlowDO.setAttachment(reqVO.getAttachment());
                financeMoneyFlowDO.setOperator(reqVO.getOperator());
                financeMoneyFlowDO.setRentCustomer(account.getCustName());
                financeMoneyFlowDO.setRenter(account.getCustName());
                financeMoneyFlowDO.setApartmentId(apartmentId);
                financeMoneyFlowDO.setApartmentName(apartmentName);
                financeMoneyFlowMapper.insert(financeMoneyFlowDO);
            });

        }
    }

    private FinanceAccountDO createOrUpdate(Long memberId, String custId, String custType, FinanceAccountDO account) {
        if (FinanceCustomerTypeEnum.ROOM.name().equals(custType)) {
            log.info("新建房屋账户 {}/{}", custId, custType);
            FinanceAccountDO account1 = buildAccountDO(custId, custType);
            CommonResult<List<CustomerVO>> result = contractRoomAccountApi.getAllCompanyCheckinRoom(memberId);
            CustomerVO v = result.getData().stream()
                    .filter(r -> r.getId().equals(memberId) && String.format("%d_%d", r.getCompanyId(), r.getRoomId())
                            .equals(custId)).findFirst().orElse(null);

            if (result.isError() || CollectionUtils.isEmpty(result.getData()) || v == null) {
                throw new ServiceException(ErrorCodeConstants.FINANCE_ACCOUNT_NO_CHECKIN_ROOM);
            }
            account1.setCustName(v.getRoomName());
            Map<String, Object> m = new HashMap<>(4);
            m.put("apartmentId", v.getApartmentId());
            m.put("apartmentName", v.getApartmentName());
            m.put("companyId", v.getCompanyId());
            m.put("companyName", v.getCompanyName());
            m.put("roomId", v.getRoomId());
            m.put("startTime", v.getStartTime());
            account1.setProp(m);
            if (account == null) {
                financeAccountMapper.insert(account1);
            } else {
                account.setCustName(v.getRoomName());
                account.setProp(m);
                financeAccountMapper.updateById(account);
            }
            return account;
        } else {
            if (account == null) {
                return create(custId, custType);
            }
        }
        return account;
    }

    private FinanceAccountDO create(String custId, String custType) {
        log.info("新建账户 {}/{}", custId, custType);
        FinanceAccountDO account = buildAccountDO(custId, custType);
        try {
            if (FinanceCustomerTypeEnum.INDIVIDUAL.name().equals(custType)) {
                CommonResult<MemberUserRespDTO> result = memberUserApi.getUserById(Long.parseLong(custId));
                if (result.isSuccess()) {
                    account.setCustName(result.getData().getName());
                    account.setMobile(result.getData().getMobile());
                }
            } else if (FinanceCustomerTypeEnum.COMPANY.name().equals(custType)) {
                CommonResult<CustomerCompanyApiRespVO> result = customerCompanyApi.getById(Long.parseLong(custId));
                if (result.isSuccess()) {
                    account.setCustName(result.getData().getCompanyName());
                    account.setMobile(result.getData().getContactsPhone());
                }
            }
        } catch (Exception e) {
            log.error("", e);
            throw new ServiceException(500, "操作失败, 请稍后再试");
        }
        financeAccountMapper.insert(account);
        return account;
    }

    @Override
    public PageResult<FinanceAccountJournalExtDO> getJournalPage(FinanceAccountJournalPageReqVO reqVO) {
        return financeAccountJournalMapper.selectPage(reqVO);
    }

    @Override
    public PageResult<FinanceAccountJournalExtDO> getJournalPageDetails(FinanceAccountJournalPageReqVO reqVO) {
        return financeAccountJournalMapper.selectPageDetails(reqVO);
    }

    @Override
    public Boolean deduct(String custId, String custType, Long roomId, String roomName, Long tenantId,
                          Collection<FinanceAccountDeductMessage> msg) throws InterruptedException {
        BigDecimal sum = msg.stream().map(FinanceAccountDeductMessage::getAmount).reduce(BigDecimal.ZERO,
                BigDecimal::add);

        try (DistributeLock lock = getLock(custId, custType)) {
            if (lock.tryLock(3, TimeUnit.SECONDS)) {
                return TenantUtils.execute(tenantId, () -> transactionTemplate.execute(status -> {
                    FinanceAccountDO account = financeAccountMapper.selectByCustIdAndCustType(custId, custType);
                    if (account == null) {
                        account = create(custId, custType);
                    }
                    BigDecimal balance = account.getBalance();
                    BigDecimal endingBalance = balance.subtract(sum);

                    FinanceAccountDO finalAccount = account;
                    List<FinanceAccountJournalDO> j = new ArrayList<>(msg.size());
                    for (FinanceAccountDeductMessage m : msg) {
                        FinanceAccountJournalDO jd = FinanceAccountJournalDO.builder()
                                .accountId(finalAccount.getId())
                                .subject(m.getCharging())
                                .tradeTime(m.getTradeTime() != null ? m.getTradeTime() : LocalDateTime.now())
                                .tradeType(AccountTradeTypeEnum.DEDUCT.name())
                                .beginningBalance(balance)
                                .endingBalance(balance.subtract(m.getAmount()))
                                .amount(m.getAmount())
                                .direction(DirectionEnum.OUT.getValue())
                                .status(FinanceCommonStatusEnum.SUCCESS.name())
                                .tradeData(JsonUtils.createObject().put("roomName", roomName)
                                        .put("roomId", roomId)
                                        .toString())
                                .operator("系统自动扣款")
                                .build();
                        jd.setTenantId(finalAccount.getTenantId());
                        balance = balance.subtract(m.getAmount());
                        j.add(jd);
                    }
                    financeAccountJournalMapper.insertBatch(j);
                    financeAccountMapper.deduct(account.getId(), sum);
                    log.info("房间 {} 账户 {} 扣费 {} 元, 剩余: {}", roomId, account.getId(), sum, endingBalance);
                    balanceWarning(account, endingBalance);
                    if (endingBalance.compareTo(BigDecimal.ZERO) < 0) {
                        financeAccountBalanceProducer.sendBalanceMessage(tenantId, endingBalance,
                                Sets.newHashSet(roomId));
                    }
                    return Boolean.TRUE;
                }));
            }
        }
        return false;
    }

    private void balanceWarning(FinanceAccountDO account, BigDecimal endingBalance) {
        FinanceAccountBalanceWarningDO warningValue =
                financeAccountBalanceWarningService.getWarningValue(account.getTenantId(), account.getCustType());
        // 余额预警
        if (warningValue != null && endingBalance.compareTo(warningValue.getWarningValue()) <= 0 &&
                (account.getLastWarningSmsSendTime() == null
                        || (account.getLastWarningSmsSendTime() != null
                        && Duration.between(account.getLastWarningSmsSendTime(), LocalDateTime.now()).toMinutes() > (warningValue.getNotificationInterval() * 60))
                )
        ) {
            if (FinanceCustomerTypeEnum.ROOM.name().equals(account.getCustType())) {
                if (account.getProp() == null || !account.getProp().containsKey("roomId")) {
                    log.info("{} {}发送余额预警, 缺少prop->roomId", account.getCustId(), account.getCustType());
                } else {
                    try {
                        CommonResult<List<ContractRoommateApiVO>> result =
                                contractApi.getRoommateByRoomId(Long.parseLong(account.getProp().get("roomId").toString()));
                        if (result.isSuccess() && !CollectionUtils.isEmpty(result.getData())) {
                            boolean sendSuccess = false;
                            for (ContractRoommateApiVO datum : result.getData()) {
                                if (PhoneUtil.isPhone(datum.getRoommatePhone())) {
                                    if (sendSms(datum.getRoommatePhone(), warningValue, endingBalance)) {
                                        sendSuccess = true;
                                    }
                                }
                            }
                            if (sendSuccess) {
                                financeAccountMapper.updateById(FinanceAccountDO.builder()
                                        .id(account.getId())
                                        .lastWarningSmsSendTime(LocalDateTime.now())
                                        .build());
                            }
                        }
                    } catch (Exception e) {
                        log.error("{} {}发送余额预警, 同住人查询异常", account.getCustId(), account.getCustType());
                    }
                }
            } else if (StringUtils.hasText(account.getMobile()) && PhoneUtil.isPhone(account.getMobile())) {
                if (sendSms(account.getMobile(), warningValue, endingBalance)) {
                    financeAccountMapper.updateById(FinanceAccountDO.builder()
                            .id(account.getId())
                            .lastWarningSmsSendTime(LocalDateTime.now())
                            .build());
                }
            }
        }
    }

    private boolean sendSms(String mobile, FinanceAccountBalanceWarningDO warningValue, BigDecimal endingBalance) {
        try {
            log.info("{}发送余额预警{},当前余额{}", mobile, warningValue.getWarningValue(), endingBalance);
            SmsSendSingleToUserReqDTO reqDTO = new SmsSendSingleToUserReqDTO();
            reqDTO.setMobile(mobile);
            reqDTO.setTemplateCode("user-account-warning");
            Map<String, Object> param = new HashMap<>(1);
            param.put("balance", warningValue.getWarningValue());
            reqDTO.setTemplateParams(param);
            CommonResult<Long> sendResult = smsSendApi.sendSingleSmsToMember(reqDTO);
            log.info("发送余额预警返回结果: {}/{}", mobile, sendResult.isSuccess());
            return sendResult.isSuccess();
        } catch (Exception e) {
            log.error("{}发送余额预警异常", mobile, e);
        }
        return false;
    }

    @Override
    public FinanceAccountDO get(String custId, String custType) {
        FinanceAccountDO account = financeAccountMapper.selectByCustIdAndCustType(custId, custType);
        if (account == null) {
            account = buildAccountDO(custId, custType);
        }
        return account;
    }

    @Override
    public PageResult<FinanceAccountVO> getRoomAccountPage(FinanceAccountPageReqVO reqVO) {
        QueryWrapper<FinanceAccountDO> query = new QueryWrapper<FinanceAccountDO>()
                .eq("cust_type", reqVO.getCustType())
                .eq(StringUtils.hasText(reqVO.getCustId()), "cust_id", reqVO.getCustId())
                .like(StringUtils.hasText(reqVO.getName()), "cust_name", reqVO.getName())
                .like(StringUtils.hasText(reqVO.getRoomName()), "cust_name", reqVO.getRoomName())
                .like(StringUtils.hasText(reqVO.getCompanyName()), "prop->>'$.companyName'", reqVO.getCompanyName())
                .eq(reqVO.getApartmentId() != null, "prop->>'$.apartmentId'", reqVO.getApartmentId());
        if (reqVO.getCreateTime() != null && reqVO.getCreateTime().length > 0) {
            query.ge("create_time", reqVO.getCreateTime()[0]);
            if (reqVO.getCreateTime().length > 1) {
                query.le("create_time", reqVO.getCreateTime()[1]);
            }
        }

        PageResult<FinanceAccountDO> page = financeAccountMapper.selectPage(reqVO, query);
        // 查询房屋合同
        List<Long> roomIds = page.getList().stream()
                .peek(p -> {
                    if (ObjectUtil.isNotNull(p)) {
                        p.setBalance(p.getBalance().setScale(2, BigDecimal.ROUND_DOWN));
                    }
                }).filter(f -> f.getProp() != null && f.getProp().containsKey(
                        "roomId"))
                .map(f -> Long.parseLong(f.getProp().get("roomId").toString())).collect(Collectors.toList());
        if (!CollectionUtils.isEmpty(roomIds)) {
            CommonResult<List<RoomContractApiRespVO>> result = contractApi.getContractByRoomIds(roomIds);
            if (result.isSuccess()) {
                // 按合同时间降序, 取最新合同结束时间
                Map<String, List<RoomContractApiRespVO>> m =
                        result.getData().stream().sorted((a, b) -> b.getStartTime().compareTo(a.getStartTime()))
                                .collect(Collectors.groupingBy(c -> String.format("%d_%d", c.getCustomerCompanyId(),
                                        c.getRoomId())));
                page.getList().forEach(v -> {
                    if (v.getProp() != null && m.containsKey(v.getCustId())) {
                        v.getProp().put("endTime", m.get(v.getCustId()).get(0).getEndTime());
                    }
                });
            }
        }
        return FinanceConvert.INSTANCE.convertToFinanceAccountPage(page);
    }

    @Override
    public PageResult<FinanceAccountVO> getPage(FinanceAccountPageReqVO reqVO) {
        List<Long> custIds = new ArrayList<>(0);
        Long total = 0L;
        Consumer<FinanceAccountVO> convert = financeAccountVO -> {
        };
        if (FinanceCustomerTypeEnum.INDIVIDUAL.name().equals(reqVO.getCustType())) {
            MemberUserPageDTO reqDTO = new MemberUserPageDTO();
            reqDTO.setName(reqVO.getName());
            reqDTO.setMobile(reqVO.getMobile());
            reqDTO.setPageNo(reqVO.getPageNo());
            reqDTO.setPageSize(reqVO.getPageSize());
            CommonResult<PageResult<MemberUserRespDTO>> result = memberUserApi.getUserPage(reqDTO);
            if (result.isSuccess() && result.getData().getTotal() > 0) {
                custIds.addAll(result.getData().getList().stream().map(MemberUserRespDTO::getId).collect(Collectors.toList()));
                Map<String, MemberUserRespDTO> map =
                        result.getData().getList().stream().collect(Collectors.toMap(m -> m.getId().toString(),
                                b -> b));
                total = result.getData().getTotal();
                convert = financeAccountVO -> {
                    MemberUserRespDTO m = map.get(financeAccountVO.getCustId());
                    financeAccountVO.setName(m.getName());
                    financeAccountVO.setMobile(m.getMobile());
                    financeAccountVO.setCreateTime(m.getCreateTime());
                };
            }
        } else if (FinanceCustomerTypeEnum.COMPANY.name().equals(reqVO.getCustType())) {
            CustomerCompanyPageApiReqVO reqDTO = new CustomerCompanyPageApiReqVO();
            reqDTO.setPageNo(reqVO.getPageNo());
            reqDTO.setPageSize(reqVO.getPageSize());
            reqDTO.setCompanyName(reqVO.getName());
            CommonResult<PageResult<CustomerCompanyApiRespVO>> result = customerCompanyApi.getCustomerCompany(reqDTO);
            if (result.isSuccess() && result.getData().getTotal() > 0) {
                custIds.addAll(result.getData().getList().stream().map(CustomerCompanyApiRespVO::getId).collect(Collectors.toList()));
                Map<String, CustomerCompanyApiRespVO> map = result.getData().getList().stream()
                        .collect(Collectors.toMap(c -> c.getId().toString(), b -> b));
                total = result.getData().getTotal();
                convert = financeAccountVO -> {
                    CustomerCompanyApiRespVO m = map.get(financeAccountVO.getCustId());
                    financeAccountVO.setName(m.getCompanyName());
                    financeAccountVO.setMobile(m.getTelephone());
                    financeAccountVO.setCreateTime(m.getCreateTime());
                };
            }
        }
        if (CollectionUtils.isEmpty(custIds)) {
            return new PageResult<>();
        }
        List<FinanceAccountDO> list = financeAccountMapper.selectList(new QueryWrapper<FinanceAccountDO>()
                .in(true, "cust_id", custIds)
                .eq("cust_type", reqVO.getCustType()));

        List<FinanceAccountVO> list2 = FinanceConvert.INSTANCE.convertToFinanceAcountVOList(list);
        if (list.size() != custIds.size()) {
            Map<String, FinanceAccountVO> m = list2.stream().collect(Collectors.toMap(FinanceAccountVO::getCustId,
                    b -> b));
            for (Long custId : custIds) {
                if (!m.containsKey(custId.toString())) {
                    FinanceAccountDO accountDO = buildAccountDO(custId.toString(), reqVO.getCustType());
                    FinanceAccountVO accountVO = FinanceConvert.INSTANCE.convert(accountDO);
                    convert.accept(accountVO);
                    list2.add(accountVO);
                } else {
                    FinanceAccountVO accountVO = m.get(custId.toString());
                    convert.accept(accountVO);
                }
            }
        } else {
            list2.forEach(convert);
        }
        list2.sort(Comparator.comparing(FinanceAccountVO::getCustId));
        //累计金额统计
        List<FinanceAccountAccumulationVO> financeAccountAccumulationVOS =
                financeAccountJournalMapper.selectAccountAccumulation(list2.stream().map(FinanceAccountVO::getId)
                        .collect(Collectors.toList()));
        list2.forEach(f -> {
            if (ObjectUtil.isNotNull(f.getBalance())) {
                f.setBalance(f.getBalance().setScale(2, BigDecimal.ROUND_DOWN));
                f.setAccumulatedRecharge(BigDecimal.ZERO);
                f.setAccumulatedHydropower(BigDecimal.ZERO);
                f.setAccumulatedWithdrawal(BigDecimal.ZERO);
            }
            //统计金额匹配
            if (Objects.nonNull(f.getId()) && CollectionUtil.isNotEmpty(financeAccountAccumulationVOS)) {
                financeAccountAccumulationVOS.stream().filter(fi -> fi.getAccountId().compareTo(f.getId()) == 0).findFirst()
                        .ifPresent(ip -> {
                            f.setAccumulatedRecharge(ObjectUtil.isNotNull(ip.getAccumulatedRecharge())
                                    ? ip.getAccumulatedRecharge().setScale(2, BigDecimal.ROUND_DOWN) : BigDecimal.ZERO);
                            f.setAccumulatedHydropower(ObjectUtil.isNotNull(ip.getAccumulatedRecharge())
                                    ? ip.getAccumulatedHydropower().setScale(2, BigDecimal.ROUND_DOWN) :
                                    BigDecimal.ZERO);
                            f.setAccumulatedWithdrawal(ObjectUtil.isNotNull(ip.getAccumulatedRecharge())
                                    ? ip.getAccumulatedWithdrawal().setScale(2, BigDecimal.ROUND_DOWN) :
                                    BigDecimal.ZERO);
                        });
            }
        });
        //个人预充值添加房号
        individualRoomName(reqVO, list2);
        return new PageResult<>(list2, total);
    }

    /**
     * 个人预充值添加房号
     *
     * @param reqVO
     * @param list2
     */
    private void individualRoomName(FinanceAccountPageReqVO reqVO, List<FinanceAccountVO> list2) {
        if (FinanceCustomerTypeEnum.INDIVIDUAL.name().equals(reqVO.getCustType()) && CollectionUtil.isNotEmpty(list2)) {
            CommonResult<List<ContractRoomMemberUserVO>> roomNameByMemberIds =
                    contractApi.getRoomNameByMemberIds(list2.stream()
                            .filter(f -> ObjectUtil.isNotEmpty(f.getCustId()))
                            .map(FinanceAccountVO::getCustId).map(Long::valueOf)
                            .collect(Collectors.toList()));
            if (roomNameByMemberIds.isSuccess() && CollectionUtil.isNotEmpty(roomNameByMemberIds.getData())) {
                List<ContractRoomMemberUserVO> data = roomNameByMemberIds.getData();
                //分组
                Map<Long, List<ContractRoomMemberUserVO>> collect =
                        data.stream().collect(Collectors.groupingBy(ContractRoomMemberUserVO::getMemberId));

                list2.forEach(f -> {
                    List<ContractRoomMemberUserVO> contractRoomMemberUserVOS = collect.get(Long.valueOf(f.getCustId()));
                    if (CollectionUtil.isNotEmpty(contractRoomMemberUserVOS)) {
                        f.setRoomNames(contractRoomMemberUserVOS.stream().map(ContractRoomMemberUserVO::getRoomName).collect(Collectors.joining(",")));
                    }
                });
            }
        }
    }

    @Override
    public void refund(FinanceAccountRefundReqVO reqVO) {
        FinanceAccountDO account = financeAccountMapper.selectByCustIdAndCustType(reqVO.getCustId(),
                reqVO.getCustType());
        try (DistributeLock lock = getLock(reqVO.getCustId(), reqVO.getCustType())) {
            if (!lock.tryLock()) {
                throw new ServiceException(ErrorCodeConstants.FINANCE_ACCOUNT_CONCURRENT_MODIFY);
            }
            if (account == null || account.getBalance().compareTo(reqVO.getRefundAmount()) < 0) {
                throw new ServiceException(ErrorCodeConstants.FINANCE_ACCOUNT_NOT_ENOUGH);
            }
            BigDecimal balance = account.getBalance();
            BigDecimal endingBalance = account.getBalance().subtract(reqVO.getRefundAmount());
            String operator = SecurityFrameworkUtils.getLoginUser().getNickname();

            transactionTemplate.executeWithoutResult(status -> {
                BankTransferReqDTO financeBankTransferDO = FinanceConvert.INSTANCE.convert(reqVO);
                if (reqVO.getApartmentId() != null) {
                    financeBankTransferDO.setApartmentId(reqVO.getApartmentId());
                    financeBankTransferDO.setApartmentName(reqVO.getApartmentName());
                } else {
                    CommonResult<List<ContractRoomReqVO>> result;
                    if (FinanceCustomerTypeEnum.INDIVIDUAL.name().equals(reqVO.getCustType())) {
                        result = contractApi.getAllContractByRoomMateUserId(Long.parseLong(reqVO.getCustId()));
                    } else {
                        result = contractApi.getAllContractByCompanyId(Long.parseLong(reqVO.getCustId()));
                    }
                    if (result.isSuccess() && !CollectionUtils.isEmpty(result.getData())) {
                        log.info("申请人所属门apartmentName:{}", result.getData().get(0).getApartmentName());
                        financeBankTransferDO.setApartmentId(result.getData().get(0).getApartmentId());
                        financeBankTransferDO.setApartmentName(result.getData().get(0).getApartmentName());
                        financeBankTransferDO.setDepartment(result.getData().get(0).getApartmentName());
                        financeBankTransferDO.setRoomId(result.getData().get(0).getRoomId());
                        financeBankTransferDO.setRoomName(result.getData().get(0).getRoomName());
                    }
                }

                //没有门店不允许钱包退费
                if (financeBankTransferDO.getApartmentId() == null) {
                    throw new ServiceException(ErrorCodeConstants.FINANCE_WITHDRAWAL_NOT_APARTMENT);
                }

                FinanceAccountJournalDO jd = FinanceAccountJournalDO.builder()
                        .accountId(account.getId())
                        .subject("退款")
                        .tradeTime(LocalDateTime.now())
                        .tradeType(AccountTradeTypeEnum.REFUND.name())
                        .beginningBalance(balance)
                        .endingBalance(endingBalance)
                        .amount(reqVO.getRefundAmount())
                        .direction(DirectionEnum.OUT.getValue())
                        .status(FinanceCommonStatusEnum.APPROVE.name())
                        .tradeBank(reqVO.getPayeeBank())
                        .tradeAccountNo(reqVO.getPayeeBankAccount())
                        .operator(operator)
                        .build();
                jd.setTenantId(account.getTenantId());


                ApproveTypeEnum typeEnum = ApproveTypeEnum.PERSONAL_WALLET_WITHDRAWAL;
                //企业钱包审批
                if (FinanceCustomerTypeEnum.COMPANY.name().equals(reqVO.getCustType())) {
                    typeEnum = ApproveTypeEnum.COMPANY_WALLET_WITHDRAWAL;

                }
                //数据组装

                financeBankTransferDO.setPayAmount(reqVO.getRefundAmount());
                financeBankTransferDO.setBizType(SourceBizTypeEnum.ACCOUNT_RECHARGE.name());
                financeBankTransferDO.setPayeeType(FinanceCustomerTypeEnum.INDIVIDUAL.name().equals(account.getCustType()) ? "租客" : "企业");
                financeBankTransferDO.setOperator(operator);
                financeBankTransferDO.setPayeeMobile(account.getMobile());
                financeBankTransferDO.setRemark(reqVO.getReason());
                financeBankTransferDO.setAttachment(reqVO.getAttachment());

                //是否审批
                CommonResult<String> mark = approvalApi.getApartmentMark(financeBankTransferDO.getApartmentId(),
                        typeEnum.getMsg());

                //如果需要审批
                if (mark.isSuccess() && org.apache.commons.lang3.StringUtils.isNotBlank(mark.getData())) {
                    financeAccountJournalMapper.insert(jd);
                    financeBankTransferDO.setBizNo(jd.getId().toString());
                    //发起审批
                    String processInstanceId = createProcessInstance(mark.getData(), financeBankTransferDO,
                            reqVO.getCustType(), getLoginUserId(), account.getCustId());
//                    jd.setProcessInstanceId(processInstanceId);
                    financeAccountJournalMapper.updateById(FinanceAccountJournalDO.builder()
                            .id(jd.getId()).processInstanceId(processInstanceId).build());

                } else {
                    jd.setStatus(FinanceCommonStatusEnum.SUCCESS.name());
                    financeAccountJournalMapper.insert(jd);
                    financeAccountMapper.deduct(account.getId(), reqVO.getRefundAmount());

                    financeBankTransferDO.setBizDate(LocalDate.now());
                    financeBankTransferDO.setBizNo(jd.getId().toString());
                    financeBankTransferService.save(financeBankTransferDO);

                    if (endingBalance.compareTo(BigDecimal.ZERO) <= 0) {
                        sendAccountOverdueMessage(account, endingBalance);
                    }
                }

            });

            log.info("账户 {} 扣费 {} 元, 剩余: {}", account.getId(), reqVO.getRefundAmount(), endingBalance);
//            if (endingBalance.compareTo(BigDecimal.ZERO) <= 0) {
//                sendAccountOverdueMessage(account, endingBalance);
//            }
        }

    }

    @Override
    public void approvalRefundResult(BpmProcessInstanceResultEvent event) {
        //同意
        if (BpmProcessInstanceResultEnum.APPROVE.getResult().equals(event.getResult())) {
            //获取流程变量
            CommonResult<Map<String, Object>> processVariablesMap =
                    bpmProcessInstanceApi.getProcessVariablesMap(event.getId());
            FinanceAccountJournalDO financeAccountJournalDO =
                    financeAccountJournalMapper.selectById(event.getBusinessKey());
            Optional.ofNullable(financeAccountJournalDO).ifPresent(d -> {
                Optional.of(financeAccountMapper.selectById(d.getAccountId())).ifPresent(a -> {
                    //获取锁
                    try (DistributeLock lock = getLock(a.getCustId(), a.getCustType())) {
                        if (!lock.tryLock()) {
                            throw new ServiceException(ErrorCodeConstants.FINANCE_ACCOUNT_CONCURRENT_MODIFY);
                        }

                        if (processVariablesMap.isSuccess() && ObjectUtil.isNotEmpty(processVariablesMap.getCheckedData())) {
                            log.info("processVariablesMap.getCheckedData(){}",
                                    JSON.toJSONString(processVariablesMap.getCheckedData()));
                            //入应付款单中

                            BankTransferReqDTO bankTransferReqDTO =
                                    JSONObject.parseObject(JSONObject.toJSONString(processVariablesMap.getCheckedData()), BankTransferReqDTO.class);
                            bankTransferReqDTO.setBizDate(LocalDate.now());
                            financeBankTransferService.save(bankTransferReqDTO);
                        }
                        //修改明细状态
                        financeAccountJournalMapper.updateById(FinanceAccountJournalDO.builder().id(d.getId())
                                .status(FinanceCommonStatusEnum.SUCCESS.name()).build());
                        //扣除余额
                        financeAccountMapper.deduct(a.getId(), d.getAmount());

                    }
                });

            });


            //其他算拒绝 修改明细状态
        } else {
            financeAccountJournalMapper.updateById(FinanceAccountJournalDO.builder().id(Long.parseLong(event.getBusinessKey()))
                    .status(FinanceCommonStatusEnum.REJECTED.name()).build());
        }


    }


    /**
     * 创建个人/企业余额提现审批
     *
     * @param mark
     * @param dto
     * @param custType
     * @param userId
     * @return
     */
    private String createProcessInstance(String mark, BankTransferReqDTO dto, String custType, Long userId,
                                         String custId) {
        Map<String, Object> map = JSONObject.parseObject(JSONObject.toJSONString(dto), Map.class);
        // 工作流参数
        BpmProcessInstanceCreateReqDTO bpmProcessInstanceCreateReqDTO = new BpmProcessInstanceCreateReqDTO();
        bpmProcessInstanceCreateReqDTO.setProcessDefinitionKey(mark);
        //默认个人
        bpmProcessInstanceCreateReqDTO.setBusinessType(BpmBusinessTypeEnum.PERSONAL_WALLET_WITHDRAWAL.name());
        //如果是企业
        if (FinanceCustomerTypeEnum.COMPANY.name().equals(custType)) {
            bpmProcessInstanceCreateReqDTO.setBusinessType(BpmBusinessTypeEnum.COMPANY_WALLET_WITHDRAWAL.name());
            CommonResult<CompanyInfoVO> companyCustomerById =
                    contractApi.getCompanyCustomerById(Long.parseLong(custId));
            //存在
            if (companyCustomerById.isSuccess() && ObjectUtil.isNotEmpty(companyCustomerById.getCheckedData())) {
                //企业联系人名称
                map.put("companyName", companyCustomerById.getCheckedData().getContactsName());
                //企业联系人手机号
                map.put("companyPhone", companyCustomerById.getCheckedData().getContactsPhone());
            }
        }
        //账户明细id
        bpmProcessInstanceCreateReqDTO.setBusinessKey(dto.getBizNo());
        //打印一下
        log.info("bpmProcessInstanceCreateReqDTO Variables :{}", JSONObject.parseObject(JSONObject.toJSONString(dto),
                Map.class));
        map.put("createName", getLoginUser().getNickname());
        bpmProcessInstanceCreateReqDTO.setVariables(map);
        bpmProcessInstanceCreateReqDTO.setTenantId(getLoginUser().getTenantId());
        bpmProcessInstanceCreateReqDTO.setServiceName(applicationName);
        log.info("processInstance userId {}, request : {}", userId,
                JsonUtils.toJsonString(bpmProcessInstanceCreateReqDTO));
        CommonResult<String> processInstance = bpmProcessInstanceApi.createProcessInstance(userId,
                bpmProcessInstanceCreateReqDTO);
        log.info("processInstance:{}", JsonUtils.toJsonString(processInstance));
        return processInstance.getCheckedData();
    }

    @Override
    public Long createJournal(LoginUser user, AppFinanceAccountRechargeReqVO reqVO) {
        // 默认充值个人账户
        String custId = user.getId().toString();
        String custType = FinanceCustomerTypeEnum.INDIVIDUAL.name();
        // 房屋账户前端传入
        if (StringUtils.hasText(reqVO.getCustType())) {
            custType = reqVO.getCustType();
        }
        if (StringUtils.hasText(reqVO.getCustId())) {
            custId = reqVO.getCustId();
        }
        if (!FinanceCustomerTypeEnum.containsValue(custType)) {
            log.error("异常类型type:{}", custType);
            throw new ServiceException(ErrorCodeConstants.FINANCE_ACCOUNT_TYPE_ERROR);
        }
        // 个人账户检查有效合同
        if (FinanceCustomerTypeEnum.INDIVIDUAL.name().equals(custType)) {
            CommonResult<List<ContractRoomReqVO>> result = contractApi.getRoomIdByUserId(user.getId());
            if (result.isError() || CollectionUtils.isEmpty(result.getData())) {
                throw new ServiceException(ErrorCodeConstants.FINANCE_ACCOUNT_NO_CONTRACT);
            }
            //判断企业下寝室长时 是否存在支付主体为房屋钱包
            if (!CollectionUtils.isEmpty(result.getData())) {
                long count =
                        result.getData().stream().filter(f ->
                                CustomerTypeEnum.PERSONAL.getCode().equals(f.getCustomerType()) ||
                                        (CustomerTypeEnum.COMPANY.getCode().equals(f.getCustomerType())
                                                && ObjectUtil.isNotEmpty(f.getOrderShareMethod())
                                                //企业只承担租金押金
                                                && f.getOrderShareMethod().compareTo(2) == 0)).count();

                if (count == 0) {
                    throw new ServiceException(ErrorCodeConstants.FINANCE_ACCOUNT_NO_CONTRACT);
                }

            }
        }
        try (DistributeLock lock = getLock(custId, custType)) {
            if (!lock.tryLock()) {
                throw new ServiceException(ErrorCodeConstants.FINANCE_ACCOUNT_CONCURRENT_MODIFY);
            }
            log.info("[recharge] 账户充值 {} {} {} {}", user.getId(), custId, custType, reqVO.getRechargeAmount());

            FinanceAccountDO account = financeAccountMapper.selectByCustIdAndCustType(custId, custType);
//            if (account == null) {
            account = createOrUpdate(user.getId(), custId, custType, account);
//            }
            BigDecimal balance = account.getBalance();
            BigDecimal endingBalance = balance.add(reqVO.getRechargeAmount());

            FinanceAccountJournalDO accountJournalDO = FinanceAccountJournalDO.builder()
                    .accountId(account.getId())
                    .subject(AccountTradeTypeEnum.ONLINE_RECHARGE.getDesc())
                    .tradeTime(LocalDateTime.now())
                    .tradeType(AccountTradeTypeEnum.ONLINE_RECHARGE.name())
                    .operator(String.format("%s(%s)", user.getNickname(), user.getMobile()))
                    .beginningBalance(balance)
                    .endingBalance(endingBalance)
                    .amount(reqVO.getRechargeAmount())
                    .direction(DirectionEnum.IN.getValue())
                    .status(FinanceCommonStatusEnum.WAITING.name())
                    .build();
            financeAccountJournalMapper.insert(accountJournalDO);
            return accountJournalDO.getId();
        }
    }

    @Override
    public void journalPayCallback(Long journalId, PayResultMessage payResultMessage) {
        boolean success = payResultMessage.getSuccess() != null && payResultMessage.getSuccess();
        if (!success) {
            financeAccountJournalMapper.updateById(FinanceAccountJournalDO.builder()
                    .id(journalId)
                    .status(FinanceCommonStatusEnum.FAILURE.name())
                    .build());
            return;
        }
        FinanceAccountJournalDO journalDO = financeAccountJournalMapper.selectById(journalId);
        if (journalDO == null) {
            log.info("租客充值回调, 流水不存在; 流水id: {}", journalId);
            return;
        }
        FinanceAccountDO account = financeAccountMapper.selectById(journalDO.getAccountId());
        try (DistributeLock lock = getLock(account.getCustId(), account.getCustType())) {
            for (int i = 0; i < 5; i++) {
                try {
                    if (lock.tryLock(1, TimeUnit.SECONDS)) {
                        // 取回调的金额, 防止下单后修改参数拉起支付
                        BigDecimal amount = BigDecimal.valueOf(payResultMessage.getPayAmount())
                                .divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
                        BigDecimal endingBalance = account.getBalance().add(amount);
                        financeAccountJournalMapper.updateById(FinanceAccountJournalDO.builder()
                                .id(journalId)
                                .beginningBalance(account.getBalance())
                                .endingBalance(endingBalance)
                                .amount(amount)
                                .tradeTime(DateUtil.toLocalDateTime(payResultMessage.getPayTime()))
                                .status(FinanceCommonStatusEnum.SUCCESS.name())
                                .build());
                        financeAccountMapper.deduct(account.getId(), amount.negate());
                        if (endingBalance.compareTo(BigDecimal.ZERO) > 0) {
                            try {
                                sendAccountOverdueMessage(account, endingBalance);
                            } catch (Exception e) {
                                log.error("", e);
                            }
                        }
                        return;
                    }
                } catch (InterruptedException e) {
                    log.warn("租客支付回调修改流水时, 获取锁失败, 重试{}", i);
                }
            }
            throw new ServiceException(500, "租客支付回调修改流水时, 获取锁失败, 重试超过5次");
        }
    }

    /**
     * 查询所有账户
     *
     * @param user
     * @return
     */
    @Override
    public List<FinanceAccountDO> getAll(LoginUser user) {
        CommonResult<List<CustomerVO>> r = contractRoomAccountApi.getAllCompanyCheckinRoom(user.getId());
        List<FinanceAccountDO> l = new ArrayList<>();
        if (r.isSuccess() && !CollectionUtils.isEmpty(r.getData())) {
            Map<String, List<CustomerVO>> accountId = r.getData().stream()
                    .collect(Collectors.groupingBy(c -> String.format("%d_%d", c.getCompanyId(), c.getRoomId())));
            // 所有房屋账户
            List<FinanceAccountDO> a = financeAccountMapper.selectList("cust_id", accountId.keySet());

            for (FinanceAccountDO accountDO : a) {
                accountDO.setCustName(accountId.get(accountDO.getCustId()).get(0).getRoomName());
                l.add(accountDO);
                accountId.remove(accountDO.getCustId());
            }
            accountId.forEach((k, v) -> {
                FinanceAccountDO newAccount = get(k, FinanceCustomerTypeEnum.ROOM.name());
                newAccount.setCustName(v.get(0).getRoomName());
                l.add(newAccount);
            });
        }
        return l;
    }

    @Override
    public void withdraw(Integer accountId, String custId, String custType, BigDecimal amount, String operator) {
        try (DistributeLock lock = getLock(custId, custType)) {
            if (!lock.tryLock()) {
                throw new ServiceException(ErrorCodeConstants.FINANCE_ACCOUNT_CONCURRENT_MODIFY);
            }
            log.info("确认提现 {} {} {}", custId, custType, amount);

            FinanceAccountDO account = financeAccountMapper.selectById(accountId);
            BigDecimal frozen = account.getFrozen();
            if (frozen.compareTo(amount) >= 0) {
                frozen = frozen.subtract(amount);
                financeAccountMapper.updateById(FinanceAccountDO.builder()
                        .id(account.getId())
                        .frozen(frozen)
                        .build());
            }
            FinanceAccountJournalDO accountJournalDO = FinanceAccountJournalDO.builder()
                    .accountId(account.getId())
                    .subject(AccountTradeTypeEnum.WITHDRAW.getDesc())
                    .tradeTime(LocalDateTime.now())
                    .tradeType(AccountTradeTypeEnum.WITHDRAW.name())
                    .operator(operator)
                    .beginningBalance(account.getBalance())
                    .endingBalance(account.getBalance())
                    .amount(amount)
                    .direction(DirectionEnum.OUT.getValue())
                    .status(FinanceCommonStatusEnum.SUCCESS.name())
                    .build();
            financeAccountJournalMapper.insert(accountJournalDO);
        }
    }

    @Override
    public FinanceWalletApplyRefundDetailsVO getWalletApplyRefundDetails(Long id) {
        FinanceAccountJournalDO financeAccountJournalDO = financeAccountJournalMapper.selectById(id);
        CommonResult<Map<String, Object>> processVariablesMap =
                bpmProcessInstanceApi.getProcessVariablesMap(financeAccountJournalDO.getProcessInstanceId());
        FinanceWalletApplyRefundDetailsVO financeWalletApplyRefundDetailsVO = new FinanceWalletApplyRefundDetailsVO();
        if (processVariablesMap.isSuccess() && ObjectUtil.isNotEmpty(processVariablesMap.getCheckedData())) {
            BankTransferReqDTO bankTransferReqDTO =
                    JSONObject.parseObject(JSONObject.toJSONString(processVariablesMap.getCheckedData()),
                            BankTransferReqDTO.class);
            financeWalletApplyRefundDetailsVO =
                    FinanceConvert.INSTANCE.convert1(bankTransferReqDTO);
            //消费前余额
            financeWalletApplyRefundDetailsVO.setBeginningBalance(financeAccountJournalDO.getBeginningBalance());
            financeWalletApplyRefundDetailsVO.setCreatTime(financeAccountJournalDO.getCreateTime());
            //获取账户类型 查找账户所有人信息
            FinanceAccountDO financeAccountDO =
                    financeAccountMapper.selectById(financeAccountJournalDO.getAccountId());
            financeWalletApplyRefundDetailsVO.setName(financeAccountDO.getCustName());
            //查询企业信息
            if (FinanceCustomerTypeEnum.COMPANY.name().equals(financeAccountDO.getCustType())) {
                //企业联系人名称
                financeWalletApplyRefundDetailsVO.setCompanyName(processVariablesMap.getCheckedData().get(
                        "companyName") != null ?
                        processVariablesMap.getCheckedData().get("companyName").toString() : null);
                //企业联系人手机号
                financeWalletApplyRefundDetailsVO.setCompanyPhone(processVariablesMap.getCheckedData().get(
                        "companyPhone") != null ?
                        processVariablesMap.getCheckedData().get("companyPhone").toString() : null);
            }
        }

        return financeWalletApplyRefundDetailsVO;
    }


    /**
     * 账户余额不足消息
     *
     * @param endingBalance
     */
    private void sendAccountOverdueMessage(FinanceAccountDO account, BigDecimal endingBalance) {
        CommonResult<List<ContractRoomReqVO>> result;
        Set<Long> roomIds = null;
        if (FinanceCustomerTypeEnum.INDIVIDUAL.name().equals(account.getCustType())) {
            result = contractApi.getRoomIdByUserId(Long.parseLong(account.getCustId()));
            roomIds = result.getCheckedData().stream().map(ContractRoomReqVO::getRoomId).collect(Collectors.toSet());
        } else if (FinanceCustomerTypeEnum.COMPANY.name().equals(account.getCustType())) {
            result = contractApi.getRoomIdByCompanyId(Long.parseLong(account.getCustId()));
            roomIds = result.getCheckedData().stream()
                    .filter(c -> c.getOrderShareMethod() != null && c.getOrderShareMethod() == 1)
                    .map(ContractRoomReqVO::getRoomId)
                    .collect(Collectors.toSet());
        } else if (FinanceCustomerTypeEnum.ROOM.name().equals(account.getCustType())) {
            String[] roomId = account.getCustId().split("_");
            roomIds = Sets.newHashSet(Long.parseLong(roomId[1].toString()));
        }
        if (!CollectionUtils.isEmpty(roomIds)) {
            financeAccountBalanceProducer.sendBalanceMessage(account.getTenantId(), endingBalance, roomIds);
        }
    }

    private FinanceAccountDO buildAccountDO(String custId, String custType) {
        return FinanceAccountDO.builder().custId(custId).custType(custType)
                .accountType(FinanceAccountTypeEnum.PREPAID.name())
                .balance(BigDecimal.ZERO)
                .frozen(BigDecimal.ZERO)
                .status(CommonStatusEnum.ENABLE.name())
                .build();
    }

    private DistributeLock getLock(String custId, String custType) {
        return DistributeLock.getLock((RedisKeyConstants.RECHARGE_LOCK.formatKey(custId, custType)));
    }

    /**
     * 个人企业余额提现
     *
     * @param reqVO
     */
    @Override
    public void walletRefund(FinanceAccountRefundReqVO reqVO) {
        try (DistributeLock lock = getLock(reqVO.getCustId(), reqVO.getCustType())) {
            if (StrUtil.isNotBlank(reqVO.getPayeeBankAccount())) {
                reqVO.setPayeeBankAccount(reqVO.getPayeeBankAccount().replaceAll("\\s", ""));
            }
            FinanceAccountDO account = financeAccountMapper.selectByCustIdAndCustType(reqVO.getCustId(),
                    reqVO.getCustType());
            if (!lock.tryLock()) {
                throw new ServiceException(ErrorCodeConstants.FINANCE_ACCOUNT_CONCURRENT_MODIFY);
            }
            if (account == null || account.getBalance().compareTo(reqVO.getRefundAmount()) < 0) {
                throw new ServiceException(ErrorCodeConstants.FINANCE_ACCOUNT_NOT_ENOUGH);
            }
            BigDecimal balance = account.getBalance();
            BigDecimal endingBalance = account.getBalance().subtract(reqVO.getRefundAmount());
            String operator = SecurityFrameworkUtils.getLoginUser().getNickname();

            transactionTemplate.executeWithoutResult(status -> {


                FinanceAccountWithdrawDO.FinanceAccountWithdrawDOBuilder builder = FinanceAccountWithdrawDO.builder();
                String no = timebaseSequenceGenerator.YYYYMMDDHHMMSS.next("lease:withdraw:no");
                LoginUser loginUser = getLoginUser();
                builder.accountId(account.getId())
                        .custId(account.getCustId())
                        .number("TX" + no)
                        .custType(account.getCustType())
                        .beginningBalance(balance)
                        .endingBalance(endingBalance)
                        .amount(reqVO.getRefundAmount())
                        //提现人申请人 申请账号信息 start
                        .applyMemberId(Long.parseLong(account.getCustId()))
                        .applyMemberPhone(account.getMobile())
                        .applyMemberName(account.getCustName())
                        //提现人 end
                        //提现人申请人 申请管家信息 start
                        .reviewMemberId(loginUser.getId())
                        .reviewMemberPhone(loginUser.getMobile())
                        .reviewMemberName(loginUser.getNickname())
                        //提现人 end
                        //备注
                        .remark(reqVO.getReason())
                        .result(FinanceAccountWithdrawResultEnum.SUBMIT.name())
                        .type(FinanceAccountWithdrawTypeEnum.APPLY.name())
                        .payeeName(reqVO.getPayeeName())
                        .payeeMobile(account.getMobile())
                        //收款账号
                        .payeeBankAccount(reqVO.getPayeeBankAccount())
                        //收款行
                        .payeeBank(reqVO.getPayeeBank())
                        //支行名称
                        .payeeBankBranch(reqVO.getPayeeBankBranch())
                        //行号
                        .payeeBankCode(reqVO.getPayeeBankCode())
                        //收款行所在省
                        .payeeBankProvince(reqVO.getPayeeBankProvince())
                        //收款行所在市
                        .payeeBankCity(reqVO.getPayeeBankCity())
                        .attachment(reqVO.getAttachment())
                        .source(FinanceAccountTypeSourceEnum.PC.name())

                ;
                FinanceAccountWithdrawDO withdrawDO = builder.build();
                CommonResult<List<ContractRoomReqVO>> result;
                if (FinanceCustomerTypeEnum.INDIVIDUAL.name().equals(reqVO.getCustType())) {
                    result = contractApi.getAllContractByRoomMateUserId(Long.parseLong(reqVO.getCustId()));
                } else {
                    result = contractApi.getAllContractByCompanyId(Long.parseLong(reqVO.getCustId()));
                    //企业id
                    withdrawDO.setCompanyId(Integer.parseInt(reqVO.getCustId()));
                    //企业名称
                    withdrawDO.setCompanyName(account.getCustName());
                    //企业联系人 企业手机号
                    CommonResult<CompanyInfoVO> companyCustomerById =
                            contractApi.getCompanyCustomerById(Long.parseLong(reqVO.getCustId()));
                    //存在
                    if (companyCustomerById.isSuccess() && ObjectUtil.isNotEmpty(companyCustomerById.getCheckedData())) {
                        withdrawDO.setCompanyContactName(companyCustomerById.getCheckedData().getContactsName());
                        withdrawDO.setCompanyContactPhone(companyCustomerById.getCheckedData().getContactsPhone());
                    }
                }

                if (result.isSuccess() && !CollectionUtils.isEmpty(result.getData())) {
                    Long apartmentId = result.getData().get(0).getApartmentId();
                    withdrawDO.setApartmentId(Integer.valueOf(apartmentId.toString()));
                    withdrawDO.setApartmentName(result.getData().get(0).getApartmentName());
                    withdrawDO.setRoomId(result.getData().get(0).getRoomId());
                    withdrawDO.setRoomName(result.getData().get(0).getRoomName());
                }
                ApproveTypeEnum typeEnum = ApproveTypeEnum.PERSONAL_WALLET_WITHDRAWAL;
                //企业钱包审批
                if (FinanceCustomerTypeEnum.COMPANY.name().equals(reqVO.getCustType())) {
                    typeEnum = ApproveTypeEnum.COMPANY_WALLET_WITHDRAWAL;

                }
                if (ObjectUtil.isEmpty(withdrawDO.getApartmentId())) {
                    throw new ServiceException(ErrorCodeConstants.FINANCE_WITHDRAWAL_NOT_APARTMENT);
                }

                //是否审批
                CommonResult<String> mark = approvalApi.getApartmentMark(Long.valueOf(withdrawDO.getApartmentId()),
                        typeEnum.getMsg());

                //如果需要审批
                if (mark.isSuccess() && org.apache.commons.lang3.StringUtils.isNotBlank(mark.getData())) {

                    //冻结钱
                    FinanceAccountDO update = FinanceAccountDO.builder()
                            .id(account.getId())
                            .frozen(account.getFrozen().add(reqVO.getRefundAmount()))
                            .balance(account.getBalance().subtract(reqVO.getRefundAmount()))
                            .build();
                    financeAccountMapper.updateById(update);
                    withdrawDO.setResult(FinanceAccountWithdrawResultEnum.APPROVAL_PENDING.name());

                    //保持余额提现数据
                    financeAccountWithdrawMapper.insert(withdrawDO);
                    //发起审批
                    String walletProcessInstance = createWalletProcessInstance(mark.getData(), withdrawDO,
                            getLoginUserId());
                    //写入审批流程键
                    withdrawDO.setProcessInstanceId(walletProcessInstance);
                    FinanceAccountWithdrawDO accountWithdrawDO =
                            FinanceAccountWithdrawDO.builder().id(withdrawDO.getId()).processInstanceId(walletProcessInstance).build();
                    financeAccountWithdrawMapper.updateById(accountWithdrawDO);

                } else {
                    //钱包直接扣减
                    FinanceAccountDO update = FinanceAccountDO.builder()
                            .id(account.getId())
                            .frozen(account.getFrozen().add(reqVO.getRefundAmount()))
                            .balance(account.getBalance().subtract(reqVO.getRefundAmount()))
                            .build();
                    financeAccountMapper.updateById(update);
                    //保持余额提现数据
                    financeAccountWithdrawMapper.insert(withdrawDO);

                }
            });
            log.info("账户 {} 扣费 {} 元, 剩余: {}", account.getId(), reqVO.getRefundAmount(), endingBalance);
        }
    }


    /**
     * 创建个人/企业余额提现审批
     *
     * @param mark
     * @param dto
     * @param userId
     * @return
     */
    private String createWalletProcessInstance(String mark, FinanceAccountWithdrawDO dto, Long userId) {
        Map<String, Object> map = JSONObject.parseObject(JSONObject.toJSONString(dto), Map.class);
        // 工作流参数
        BpmProcessInstanceCreateReqDTO bpmProcessInstanceCreateReqDTO = new BpmProcessInstanceCreateReqDTO();
        bpmProcessInstanceCreateReqDTO.setProcessDefinitionKey(mark);
        //默认个人
        bpmProcessInstanceCreateReqDTO.setBusinessType(BpmBusinessTypeEnum.PERSONAL_WALLET_WITHDRAWAL.name());
        //如果是企业
        if (FinanceCustomerTypeEnum.COMPANY.name().equals(dto.getCustType())) {
            bpmProcessInstanceCreateReqDTO.setBusinessType(BpmBusinessTypeEnum.COMPANY_WALLET_WITHDRAWAL.name());
        }
        //余额申请表id
        bpmProcessInstanceCreateReqDTO.setBusinessKey(String.valueOf(dto.getId()));
        //打印一下
        log.info("bpmProcessInstanceCreateReqDTO Variables :{}", JSONObject.parseObject(JSONObject.toJSONString(dto),
                Map.class));
        map.put("createName", getLoginUser().getNickname());
        bpmProcessInstanceCreateReqDTO.setVariables(map);
        bpmProcessInstanceCreateReqDTO.setTenantId(getLoginUser().getTenantId());
        bpmProcessInstanceCreateReqDTO.setServiceName(applicationName);

        BpmProcessInstanceCreateReqDTO.ExtendDto extendDto = new BpmProcessInstanceCreateReqDTO.ExtendDto();
        extendDto.setRoomId(Objects.nonNull(dto.getRoomId()) ? dto.getRoomId().toString() : null);
//        extendDto.setContractId(contractRoomDO.getId());
//        extendDto.setContractNo(contractRoomDO.getContractNo());
        extendDto.setRoomName(dto.getRoomName());
        extendDto.setApartmentId(dto.getApartmentId() != null ? Long.valueOf(dto.getApartmentId()) : null);
        extendDto.setApartmentName(dto.getApartmentName());
        extendDto.setCustomName(dto.getApplyMemberName());
        extendDto.setCustomPhone(dto.getApplyMemberPhone());
        bpmProcessInstanceCreateReqDTO.setExtendDto(extendDto);

        log.info("processInstance userId {}, request : {}", userId,
                JsonUtils.toJsonString(bpmProcessInstanceCreateReqDTO));
        CommonResult<String> processInstance = bpmProcessInstanceApi.createProcessInstance(userId,
                bpmProcessInstanceCreateReqDTO);
        log.info("processInstance:{}", JsonUtils.toJsonString(processInstance));
        return processInstance.getCheckedData();
    }

    /**
     * 个人企业钱包余额体现回调
     *
     * @param event
     */
    @Override
    public void approvalWalletRefundResult(BpmProcessInstanceResultEvent event) {

        FinanceAccountWithdrawDO accountWithdrawDO = financeAccountWithdrawMapper.selectById(event.getBusinessKey());
        Optional.ofNullable(accountWithdrawDO).ifPresent(f -> {
            //获取锁
            try (DistributeLock lock = getLock(f.getCustId(), f.getCustType())) {
                if (!lock.tryLock()) {
                    throw new ServiceException(ErrorCodeConstants.FINANCE_ACCOUNT_CONCURRENT_MODIFY);
                }
                FinanceAccountDO financeAccountDO = financeAccountMapper.selectById(f.getAccountId());

                Optional.ofNullable(financeAccountDO).ifPresent(a -> {
                    //同意
                    if (BpmProcessInstanceResultEnum.APPROVE.getResult().equals(event.getResult())) {
                        //同意冻结钱减少
//                        FinanceAccountDO updateAcount =
//                                FinanceAccountDO.builder().id(f.getAccountId()).frozen(a.getFrozen().subtract(f
//                                .getAmount())).build();
//                        financeAccountMapper.updateById(updateAcount);

                        financeAccountWithdrawMapper.updateById(FinanceAccountWithdrawDO.builder()
                                .id(f.getId()).result(FinanceAccountWithdrawResultEnum.SUBMIT.name()).build());

                        //其他算拒绝 修改明细状态
                    } else {
                        //拒绝余额回滚
                        FinanceAccountDO updateAcount = FinanceAccountDO.builder()
                                .id(a.getId())
                                .frozen(a.getFrozen().subtract(f.getAmount()))
                                .balance(a.getBalance().add(f.getAmount()))
                                .build();
                        financeAccountMapper.updateById(updateAcount);

                        financeAccountWithdrawMapper.updateById(FinanceAccountWithdrawDO.builder()
                                .id(f.getId()).result(FinanceAccountWithdrawResultEnum.REJECT.name()).build());

                    }
                });


            }
        });


    }

    @Override
    public FinanceAccountWithdrawDO getAccountWithdrawById(Long id) {
        return financeAccountWithdrawMapper.selectById(id);
    }

    @Override
    public List<FinanceAccountWithdrawDO> selectListAccountWithdrawByIds(List<Long> ids) {
        return financeAccountWithdrawMapper.selectList(new LambdaQueryWrapper<FinanceAccountWithdrawDO>()
                .in(FinanceAccountWithdrawDO::getId, ids)
                .orderByDesc(FinanceAccountWithdrawDO::getId));
    }

    @Override
    public void getPdf(List<FinanceAccountWithdrawDO> financeAccountWithdrawDOS, PdfUtils.PdfDoc doc, Font header,
                       Font bold) throws Exception {
        List<MemberUserRespDTO> memberUserRespDTOList = new ArrayList<>();
        //非企业
        if (!FinanceCustomerTypeEnum.COMPANY.name().equals(financeAccountWithdrawDOS.get(0).getCustType())) {
            List<Long> collect = financeAccountWithdrawDOS.stream()
                    .filter(fm -> ObjectUtil.isNotEmpty(fm.getApplyMemberId())).map(FinanceAccountWithdrawDO::getApplyMemberId)
                    .collect(Collectors.toList());
            CommonResult<List<MemberUserRespDTO>> usersByIds = memberUserApi.getUsersByIds(collect);
            //成功 并且存在
            if (usersByIds.isSuccess() && ObjectUtil.isNotEmpty(usersByIds.getData())) {
                memberUserRespDTOList = usersByIds.getData();
            }
        }
        for (FinanceAccountWithdrawDO vo : financeAccountWithdrawDOS) {
            Paragraph p = new Paragraph(DateUtil.format(new Date(), "yyyy/MM/dd"), PdfUtils.PdfDoc.baseFont());
            p.setIndentationLeft(-12);
            p.setSpacingAfter(10);
            p.setSpacingBefore(0);
            doc.getDoc().add(p);
            List<MemberUserRespDTO> finalMemberUserRespDTOList = memberUserRespDTOList;
            doc.addTable(5, tb -> {
                        String reson = "";
                        //非企业身份证号码
                        if (!FinanceCustomerTypeEnum.COMPANY.name().equals(vo.getCustType()) && CollectionUtil.isNotEmpty(finalMemberUserRespDTOList)) {
                            Optional<MemberUserRespDTO> first =
                                    finalMemberUserRespDTOList.stream().filter(ff -> ff.getId().compareTo(vo.getApplyMemberId()) == 0).findFirst();
                            if (first.isPresent()) {
                                reson = String.format("(身份证%s)", StrUtil.isNotBlank(first.get().getCardNo()) ?
                                        first.get().getCardNo() : "");
                            }
                        }
                        String title = "个人余额";
                        if (FinanceCustomerTypeEnum.COMPANY.name().equals(vo.getCustType())) {
                            title = "企业余额";
                        } else if (FinanceCustomerTypeEnum.COMPANY.name().equals(vo.getCustType())) {
                            title = "房屋员工";
                        }
                        // 主表单信息
                        tb.addCell(title + "提现报告", 5, header, cell -> {
                                    cell.setPaddingTop(5);
                                    cell.setPaddingBottom(10);
                                })
                                .addCell("提现信息", 5, bold, Element.ALIGN_LEFT)
                                .addCell("提现人").addCell(vo.getApplyMemberName())
                                .addCell("发起时间").addCell(DateUtil.format(vo.getCreateTime(), "yyyy/MM/dd HH:mm:ss"), 2)
                                .addCell("提现金额").addCell("￥" + vo.getAmount(), 4)
                                .addCell("事由").addCell(String.format("%s %s 申请 提现",
                                        FinanceCustomerTypeEnum.ROOM.name().equals(vo.getCustType()) ?
                                                vo.getCompanyName() + "员工" + vo.getApplyMemberName() :
                                                vo.getApplyMemberName()
                                        , reson), 4)
                                .addCell("收款信息", 5, bold, Element.ALIGN_LEFT)
                                .addCell("收款账户类型").addCell(FinanceCustomerTypeEnum.COMPANY.name().equals(vo.getCustType()) ? "企业" : "个人")
                                .addCell("收款账户银行").addCell(vo.getPayeeBank(), 2)
                                .addCell("收款账户名称").addCell(vo.getPayeeName(), 4)
                                .addCell("收款支行名称").addCell(vo.getPayeeBankBranch(), 4)
                                .addCell("收款账户行号").addCell(vo.getPayeeBankCode(), 4)
                                .addCell("收款银行账号").addCell(vo.getPayeeBankAccount(), 4)
                                .addCell("预留手机号码").addCell(vo.getPayeeMobile(), 4);

                        //房屋钱包提现
                        if (FinanceCustomerTypeEnum.ROOM.name().equals(vo.getCustType())) {
                            tb.addCell("协商记录", 5, bold, Element.ALIGN_LEFT);
                            PdfUtils.Table nestedTable = new PdfUtils.Table(4).addCell("序号", "同住人", "审核意见", "确认时间");
                            //获取同住人
                            List<FinanceAccountWithdrawDO> cohabitantList =
                                    financeAccountWithdrawMapper.selectList(new LambdaQueryWrapper<FinanceAccountWithdrawDO>().eq(FinanceAccountWithdrawDO::getRefId, vo.getId())
                                            .orderByDesc(FinanceAccountWithdrawDO::getId));
                            if (CollectionUtil.isNotEmpty(cohabitantList)) {
                                for (int i = 0; i < cohabitantList.size(); i++) {
                                    nestedTable.addCell(String.valueOf(i + 1),
                                            cohabitantList.get(i).getReviewMemberName()
                                            ,
                                            FinanceAccountWithdrawResultEnum.AGREE.name().equals(cohabitantList.get(i).getResult()) ? "已同意" : "已拒绝"
                                            , cohabitantList.get(i).getAgreeTime() != null ?
                                                    DateUtil.format(cohabitantList.get(i).getAgreeTime(), "yyyy/MM/dd" +
                                                            " " +
                                                            "HH:mm:ss") : ""
                                    );
                                }
                            }

                            nestedTable.options(t -> {
                                t.setWidthPercentage(105);
                                try {
                                    t.setWidths(new float[]{20f, 25f, 30f, 20f});
                                } catch (DocumentException ignore) {
                                }
                            });
                            tb.addCell(nestedTable);
                        }

                        if (org.apache.commons.lang3.StringUtils.isNotBlank(vo.getProcessInstanceId())) {
                            tb.addCell("处理信息记录", 5, bold, Element.ALIGN_LEFT);
                            List<BpmTaskRespDTO> tasklist =
                                    bpmProcessInstanceApi.getAllTaskListByProcessInstanceId(vo.getProcessInstanceId());
                            PdfUtils.Table nestedTable = new PdfUtils.Table(5).addCell("处理人", "处理步骤", "审核意见", "处理结果",
                                    "处理时间");
                            if (CollectionUtil.isNotEmpty(tasklist)) {
                                for (BpmTaskRespDTO bpmTaskRespDTO : tasklist) {
                                    String handler =
                                            bpmTaskRespDTO.getAssigneeUsers().stream().findFirst().map(BpmTaskRespDTO.User::getNickname).orElse("");
                                    nestedTable.addCell(handler, bpmTaskRespDTO.getName(), bpmTaskRespDTO.getReason()
                                            , BpmProcessInstanceResultEnum.getResult(bpmTaskRespDTO.getResult()),
                                            bpmTaskRespDTO.getEndTime() != null ?
                                                    DateUtil.format(bpmTaskRespDTO.getEndTime(), "yyyy/MM/dd " +
                                                            "HH:mm:ss") : "");
                                }
                            }
//                            nestedTable.options(t -> {
//                                t.setWidthPercentage(105);
//                                try {
//                                    t.setWidths(new float[]{20f, 25f, 15f, 25f, 20f});
//                                } catch (DocumentException ignore) {
//                                }
//                            });
                            tb.addCell(nestedTable);
                        }
                        tb.addCell(String.format("备注 : %s", StrUtil.isNotBlank(vo.getRemark()) ? vo.getRemark() : "")
                                , 5, Element.ALIGN_LEFT);
                        tb.options(t -> {
                            t.setWidthPercentage(105);
                            try {
                                t.setWidths(new float[]{20f, 25f, 15f, 25f, 20f});
                            } catch (DocumentException ignore) {
                            }
                        });
                    }
            );
            doc.getDoc().newPage();
        }
    }

    @Override
    public void updateMemberUser(Long userId, String userName, String mobile, Long oldUserId) {
        financeAccountMapper.updateMemberUser(userId, userName, mobile, oldUserId);
    }

    /**
     * 个人账户合并
     *
     * @param userId       合并至用户id
     * @param oldUserId    原用户id
     * @param customerType 账户类型
     */
    @Override
    public void individuAlaccountConsolidation(String userId, String phone, String oldUserId,
                                               FinanceCustomerTypeEnum customerType) {
        //开启锁
        try (DistributeLock lock = getLock(oldUserId.toString(), customerType.name())) {
            if (!lock.tryLock()) {
                throw new ServiceException(ErrorCodeConstants.FINANCE_ACCOUNT_CONCURRENT_MODIFY);
            }
            //原账户
            FinanceAccountDO oldFinanceAccountDO = financeAccountMapper.selectByCustIdAndCustType(oldUserId,
                    customerType.name());
            //当前账户
            FinanceAccountDO financeAccountDO = financeAccountMapper.selectByCustIdAndCustType(userId,
                    customerType.name());
            //无老账户
            if (ObjectUtil.isEmpty(oldFinanceAccountDO)) {
                return;
            } else {
                Long aLong = financeAccountJournalMapper.selectCount(FinanceAccountJournalDO::getAccountId,
                        oldFinanceAccountDO.getId());
                //老账户为0并且无任何流水存在
                if (oldFinanceAccountDO.getBalance().compareTo(BigDecimal.ZERO) == 0 && aLong == 0) {
                    return;
                }
            }

            //合并至账户不存在 直接修改原账户信息
            if (ObjectUtil.isEmpty(financeAccountDO)) {
                //原操作记录
                List<AccountMigrationDO> accountMigrationList = oldFinanceAccountDO.getAccountMigrationList();
                if (accountMigrationList == null) {
                    accountMigrationList = new ArrayList<>();
                }
                accountMigrationList.add(AccountMigrationDO.builder()
                        .userId(userId)
                        .phone(phone)
                        .oldUserId(oldUserId)
                        .oldPhone(oldFinanceAccountDO.getMobile())
                        .amount(oldFinanceAccountDO.getBalance())
                        //修改至
                        .remark(String.format("%s update %s", oldUserId, userId))
                        .operationTime(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")))
                        .build());
                financeAccountMapper.updateById(FinanceAccountDO.builder().id(oldFinanceAccountDO.getId())
                        .custId(userId)
                        .accountMigrationList(accountMigrationList)
                        .mobile(phone)
                        .build());
                //原账户存在 采用合并
            } else {
                AccountMigrationDO build = AccountMigrationDO.builder()
                        .userId(userId)
                        .phone(phone)
                        .oldUserId(oldUserId)
                        .oldPhone(oldFinanceAccountDO.getMobile())
                        .amount(oldFinanceAccountDO.getBalance())
                        .oldAccountId((long) oldFinanceAccountDO.getId())
                        .accountId((long) financeAccountDO.getId())
                        //合并至
                        .remark(String.format("%s merge %s", oldUserId, userId))
                        .operationTime(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")))
                        .build();
                //原操作记录
                List<AccountMigrationDO> oldAccountMigrationList = oldFinanceAccountDO.getAccountMigrationList();
                if (oldAccountMigrationList == null) {
                    oldAccountMigrationList = new ArrayList<>();
                }
                //流出
                build.setFlowType("out");
                oldAccountMigrationList.add(build);
                //修改原有账户金额归零 冻结金额不去操作
                financeAccountMapper.updateById(FinanceAccountDO.builder().id(oldFinanceAccountDO.getId())
                        .accountMigrationList(oldAccountMigrationList)
                        .balance(BigDecimal.ZERO)
                        .build());
                //添加流出记录 金额不为0的情况下
                if (oldFinanceAccountDO.getBalance().compareTo(BigDecimal.ZERO) != 0) {
                    FinanceAccountJournalDO oldAccountJournalDO = FinanceAccountJournalDO.builder()
                            .accountId(oldFinanceAccountDO.getId())
                            .subject("修改手机号")
                            .tradeTime(LocalDateTime.now())
                            .tradeType(AccountTradeTypeEnum.OFFLINE_RECHARGE.name())
//                        .operator(operator)
                            .beginningBalance(oldFinanceAccountDO.getBalance())
                            .endingBalance(BigDecimal.ZERO)
                            .amount(oldFinanceAccountDO.getBalance().abs())
                            .direction(oldFinanceAccountDO.getBalance().compareTo(BigDecimal.ZERO) > 0 ?
                                    DirectionEnum.OUT.getValue() : DirectionEnum.IN.getValue())
                            .status(FinanceCommonStatusEnum.SUCCESS.name())
                            .build();
                    financeAccountJournalMapper.insert(oldAccountJournalDO);
                }

                //新账户操作记录
                List<AccountMigrationDO> accountMigrationList = financeAccountDO.getAccountMigrationList();
                if (accountMigrationList == null) {
                    accountMigrationList = new ArrayList<>();
                }
                //流入
                build.setFlowType("int");
                accountMigrationList.add(build);
                //修改新账户余额
                financeAccountMapper.updateById(FinanceAccountDO.builder().id(financeAccountDO.getId())
                        .accountMigrationList(accountMigrationList)
                        .balance(financeAccountDO.getBalance().add(oldFinanceAccountDO.getBalance()))
                        .build());
                //添加流入金额不为0进行记录
                if (oldFinanceAccountDO.getBalance().compareTo(BigDecimal.ZERO) != 0) {
                    FinanceAccountJournalDO accountJournalDO = FinanceAccountJournalDO.builder()
                            .accountId(financeAccountDO.getId())
                            .subject("修改手机号")
                            .tradeTime(LocalDateTime.now())
                            .tradeType(AccountTradeTypeEnum.OFFLINE_RECHARGE.name())
//                        .operator(operator)
                            .beginningBalance(financeAccountDO.getBalance())
                            .endingBalance(financeAccountDO.getBalance().add(oldFinanceAccountDO.getBalance()))
                            .amount(oldFinanceAccountDO.getBalance().abs())
                            .direction(oldFinanceAccountDO.getBalance().compareTo(BigDecimal.ZERO) > 0 ?
                                    DirectionEnum.IN.getValue() : DirectionEnum.OUT.getValue())
                            .status(FinanceCommonStatusEnum.SUCCESS.name())
                            .build();
                    financeAccountJournalMapper.insert(accountJournalDO);
                }

            }
        }
    }

    /**
     * 个人修改手机号
     *
     * @param userId
     * @param phone
     */
    @Override
    public void individualAccountUpdatePhone(String userId, String phone) {
        FinanceAccountDO financeAccountDO = financeAccountMapper.selectByCustIdAndCustType(userId,
                FinanceCustomerTypeEnum.INDIVIDUAL.name());
        if(ObjectUtil.isNotNull(financeAccountDO)){
            financeAccountMapper.updateById(FinanceAccountDO.builder().id(financeAccountDO.getId()).mobile(phone).build());
        }
    }

    /**
     * app个人余额提现
     *
     * @param reqVO
     */
    @Override
    public void appWithdrawal(AppFinanceAccountRefundReqVO reqVO) {
        LoginUser loginUser = getLoginUser();
        String custId = getLoginUser().getId().toString();
        String custType = FinanceCustomerTypeEnum.INDIVIDUAL.name();
        try (DistributeLock lock = getLock(custId, custType)) {
            if (StrUtil.isNotBlank(reqVO.getPayeeBankAccount())) {
                reqVO.setPayeeBankAccount(reqVO.getPayeeBankAccount().replaceAll("\\s", ""));
            }
            FinanceAccountDO account = financeAccountMapper.selectByCustIdAndCustType(custId,
                    custType);
            if (!lock.tryLock()) {
                throw new ServiceException(ErrorCodeConstants.FINANCE_ACCOUNT_CONCURRENT_MODIFY);
            }
            if (account == null || account.getBalance().compareTo(reqVO.getRefundAmount()) < 0) {
                throw new ServiceException(ErrorCodeConstants.FINANCE_ACCOUNT_NOT_ENOUGH);
            }
            BigDecimal balance = account.getBalance();
            BigDecimal endingBalance = account.getBalance().subtract(reqVO.getRefundAmount());
            transactionTemplate.executeWithoutResult(status -> {
                FinanceAccountWithdrawDO.FinanceAccountWithdrawDOBuilder builder = FinanceAccountWithdrawDO.builder();
                String no = timebaseSequenceGenerator.YYYYMMDDHHMMSS.next("lease:withdraw:no");

                builder.accountId(account.getId())
                        .custId(account.getCustId())
                        .number("TX" + no)
                        .custType(account.getCustType())
                        .beginningBalance(balance)
                        .endingBalance(endingBalance)
                        .amount(reqVO.getRefundAmount())
                        //提现人申请人 申请账号信息 start
                        .applyMemberId(loginUser.getId())
                        .applyMemberPhone(loginUser.getMobile())
                        .applyMemberName(loginUser.getNickname())
                        //提现人 end
                        //提现人申请人 start
                        .reviewMemberId(loginUser.getId())
                        .reviewMemberPhone(loginUser.getMobile())
                        .reviewMemberName(loginUser.getNickname())
                        //提现人 end
                        //备注
                        .remark(String.format("个人余额提现,提现人:%s,发起时间:%s", getLoginUser().getNickname(),
                                LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))))
                        .result(FinanceAccountWithdrawResultEnum.SUBMIT.name())
                        .type(FinanceAccountWithdrawTypeEnum.APPLY.name())
                        .payeeName(reqVO.getPayeeName())
                        .payeeMobile(reqVO.getPayeeMobile())
                        //收款账号
                        .payeeBankAccount(reqVO.getPayeeBankAccount())
                        //收款行
                        .payeeBank(reqVO.getPayeeBank())
                        //支行名称
                        .payeeBankBranch(reqVO.getPayeeBankBranch())
                        //行号
                        .payeeBankCode(reqVO.getPayeeBankCode())
                        //收款行所在省
                        .payeeBankProvince(reqVO.getPayeeBankProvince())
                        //收款行所在市
                        .payeeBankCity(reqVO.getPayeeBankCity())
                        //流入app
                        .source(FinanceAccountTypeSourceEnum.APP.name())

                ;
                FinanceAccountWithdrawDO withdrawDO = builder.build();
                CommonResult<List<ContractRoomReqVO>> result =
                        contractApi.getAllContractByRoomMateUserId(Long.parseLong(custId));

                if (result.isSuccess() && !CollectionUtils.isEmpty(result.getData())) {
                    Long apartmentId = result.getData().get(0).getApartmentId();
                    withdrawDO.setApartmentId(Integer.valueOf(apartmentId.toString()));
                    withdrawDO.setApartmentName(result.getData().get(0).getApartmentName());
                    withdrawDO.setRoomId(result.getData().get(0).getRoomId());
                    withdrawDO.setRoomName(result.getData().get(0).getRoomName());
                }
                ApproveTypeEnum typeEnum = ApproveTypeEnum.PERSONAL_WALLET_WITHDRAWAL;

                if (ObjectUtil.isEmpty(withdrawDO.getApartmentId())) {
                    throw new ServiceException(ErrorCodeConstants.FINANCE_WITHDRAWAL_NOT_APARTMENT);
                }

                //是否审批
                CommonResult<String> mark = approvalApi.getApartmentMark(Long.valueOf(withdrawDO.getApartmentId()),
                        typeEnum.getMsg());

                //如果需要审批
                if (mark.isSuccess() && org.apache.commons.lang3.StringUtils.isNotBlank(mark.getData())) {

                    //冻结钱
                    FinanceAccountDO update = FinanceAccountDO.builder()
                            .id(account.getId())
                            .frozen(account.getFrozen().add(reqVO.getRefundAmount()))
                            .balance(account.getBalance().subtract(reqVO.getRefundAmount()))
                            .build();
                    financeAccountMapper.updateById(update);
                    withdrawDO.setResult(FinanceAccountWithdrawResultEnum.APPROVAL_PENDING.name());

                    //保持余额提现数据
                    financeAccountWithdrawMapper.insert(withdrawDO);
                    //发起审批 用户端发起直接给0
                    String walletProcessInstance = createWalletProcessInstance(mark.getData(), withdrawDO,
                            0L);
                    //写入审批流程键
                    withdrawDO.setProcessInstanceId(walletProcessInstance);
                    FinanceAccountWithdrawDO accountWithdrawDO =
                            FinanceAccountWithdrawDO.builder().id(withdrawDO.getId()).processInstanceId(walletProcessInstance).build();
                    financeAccountWithdrawMapper.updateById(accountWithdrawDO);

                } else {
                    //钱包直接扣减
                    FinanceAccountDO update = FinanceAccountDO.builder()
                            .id(account.getId())
                            .frozen(account.getFrozen().add(reqVO.getRefundAmount()))
                            .balance(account.getBalance().subtract(reqVO.getRefundAmount()))
                            .build();
                    financeAccountMapper.updateById(update);
                    //保持余额提现数据
                    financeAccountWithdrawMapper.insert(withdrawDO);

                }
            });
            log.info("账户 {} 扣费 {} 元, 剩余: {}", account.getId(), reqVO.getRefundAmount(), endingBalance);
        }
    }
}
